Skip to content

Refuse tokens that carry no groups claim - #17

Merged
dabeckham merged 2 commits into
mainfrom
fix/require-groups-claim
Jul 26, 2026
Merged

Refuse tokens that carry no groups claim#17
dabeckham merged 2 commits into
mainfrom
fix/require-groups-claim

Conversation

@dabeckham

Copy link
Copy Markdown
Owner

The problem

The workspace is gated in two places on purpose — Authentik binds the
application to the dropbox group, and the app re-checks the groups claim.
The second check exists precisely so the workspace does not depend on the first.
But when the claim was absent, it fell back to trusting the first — in the one
case where the backstop was the only thing left to do its job.

Absence of the claim does not mean the user belongs to nothing. It means the
provider's configuration has drifted, and the honest answer to "is this user a
member?" is that we cannot tell. So we stop saying yes.

Checked before changing the default

Locking the only two members out of their own file store would be a poor way to
discover the mapping was broken, so this was verified against the live provider
rather than assumed:

  • A Beckham Share - OIDC groups scope mapping is bound to the provider,
    returning {"groups": [group.name for group in request.user.ak_groups.all()]}.
  • Evaluated against both real accounts it yields a populated list containing
    dropbox
    .
  • The app requests the groups scope — confirmed in the authorization request
    the live provider accepts.
  • The current log window covers four sign-ins and zero fallback warnings.

Nobody is relying on the fallback, and existing signed-in sessions carry the
claim, so no one is logged out by this.

The escape hatch

ALLOW_MISSING_GROUPS_CLAIM, default false.

Kept deliberately. If that scope mapping ever breaks — an Authentik upgrade, a
fat-fingered edit — the failure mode without a lever is "both members locked out
of their own files while someone repairs an identity provider under pressure".
That is its own kind of outage.

It re-opens the gap it exists to close, so it is not quiet about it: the app
logs a warning at startup for as long as it is set, and again on every
admitted request. docs/SECURITY.md says to clear it once the mapping is fixed.

Verification

Backend suite: 28 tests pass, including three new ones — a groupless token
is refused at /app, is refused by the management API, and is admitted only
when the flag is deliberately set.

The workspace is gated in two places on purpose: Authentik binds the
application to the dropbox group, and the app re-checks the groups claim. The
second check existed to not depend on the first — but when the claim was
absent it fell back to trusting the first, which is the one case where the
backstop was the only thing left to do its job.

Absence of the claim does not mean the user belongs to nothing. It means the
provider's configuration has drifted, and the honest answer to "is this user a
member" is that we cannot tell.

Checked the live provider before changing the default, rather than assuming:

- a "Beckham Share - OIDC groups" scope mapping is bound to the provider and
  returns {"groups": [g.name for g in user.ak_groups.all()]}
- evaluated against both accounts it yields a populated list containing
  dropbox
- the app requests the groups scope in the authorization request
- the current log window covers four sign-ins and no fallback warnings

So nobody is relying on the fallback and nobody gets locked out.

ALLOW_MISSING_GROUPS_CLAIM (default false) keeps a way back in if that mapping
ever breaks, since locking the only two members out of their own files while
repairing an identity provider is its own kind of outage. It re-opens the gap
it exists to close, so the app warns about it at startup for as long as it is
set.
@dabeckham
dabeckham merged commit cf7725b into main Jul 26, 2026
2 checks passed
@dabeckham
dabeckham deleted the fix/require-groups-claim branch July 26, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant